<!--
 * Copyright 2011, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>Toon Shading</title>
<style>
html, body {
  width: 100%;
  height: 100%;
  border: 0px;
  padding: 0px;
  margin: 0px;
  background-color: dark-red;
  font-family: sans-serif;
  overflow: hidden;
  color: #fff;
}
a {
 color: #fff;
}
#info {
    font-size: small;
    position: absolute;
  	top: 10px; 
  	left: 100px;
  	z-index: 2;
    background: rgba(0,0,0,0.5);
    border-radius: 10px;
    padding: 10px;
}
CANVAS {
  background-color: gray;
}
#uiContainer {
  z-index: 2;
  position: absolute;
  top: 10px;
  right: 20px;
  width: 520px;
  background: rgba(0,0,0,0.5);
  color: white;
  font-size: xx-small;
  border-radius: 10px;
  padding: 10px;
}
#ui {
}
#advanced {
  color: gray;
}
#fpsContainer {
  position: absolute;
  top: 10px;
  left: 20px;
  z-index: 2;
  color: white;
  background-color: rgba(0,0,0,0.5);
  border-radius: 10px;
  padding: 10px;
}
.clickable {
  cursor: pointer;
}
#viewContainer {
  width: 100%;
  height: 100%;
}
#ramps {
}
#ramps .ramp {
  border: 1px solid gray;
  padding: 5px;
  margin: 5px;
}
#ramps .ramp-name {
  font-size: small;
}
#ramps .ramp .ramp-colors canvas {
  width: 100%;
  height: 16px;
}
</style>  
<link href="../jquery-ui-1.8.2.custom/css/ui-lightness/jquery-ui-1.8.2.custom.css" rel="stylesheet" />
<link href="../colorpicker/css/colorpicker.css" rel="stylesheet" />
<link href="../gradient-editor/gradient-editor.css" rel="stylesheet" />
<script src="../jquery-ui-1.8.2.custom/js/jquery-1.4.2.min.js"></script>
<script src="../jquery-ui-1.8.2.custom/js/jquery-ui-1.8.2.custom.min.js"></script>
<script src="../colorpicker/js/colorpicker.js"></script>
<script src="../gradient-editor/gradient-editor.js"></script>
<script src="../tdl/base.js"></script>
<script>
tdl.require('tdl.buffers');
tdl.require('tdl.fast');
tdl.require('tdl.fps');
tdl.require('tdl.log');
tdl.require('tdl.math');
tdl.require('tdl.models');
tdl.require('tdl.primitives');
tdl.require('tdl.programs');
tdl.require('tdl.textures');
tdl.require('tdl.webgl');

// globals
var gl;                   // the gl context.
var canvas;               // the canvas
var math;                 // the math lib.
var fast;                 // the fast math lib.

var g_rampTexture;
var g_eyeSpeed          = 0;
var g_eyeHeight         = 0;
var g_eyeRadius         = 5;
var g_lightOffset       = [10, 10, 10];

var g_ramps = [
  {name: "standard",
   stops: [
     {position: 0.00, color: "#000000"},
     {position: 1.00, color: "#ffffff"}
   ]
  },
  {name: "2 tone",
   stops: [
    {position: 0.500, color: "#808080"},
    {position: 0.501, color: "#ffffff"}
   ]
  },
  {name: "3 tone",
   stops: [
    {position: 0.330, color: "#404040"},
    {position: 0.331, color: "#808080"},
    {position: 0.666, color: "#808080"},
    {position: 0.667, color: "#ffffff"}
   ]
  },
  {name: "8 tone",
   stops: [
    {position: 0.125, color: "#000000"},
    {position: 0.126, color: "#242424"},
    {position: 0.250, color: "#242424"},
    {position: 0.251, color: "#484848"},
    {position: 0.375, color: "#484848"},
    {position: 0.376, color: "#6d6d6d"},
    {position: 0.500, color: "#6d6d6d"},
    {position: 0.501, color: "#919191"},
    {position: 0.625, color: "#919191"},
    {position: 0.626, color: "#b6b6b6"},
    {position: 0.750, color: "#b6b6b6"},
    {position: 0.751, color: "#dadada"},
    {position: 0.875, color: "#dadada"},
    {position: 0.876, color: "#ffffff"}
   ]
  },
  {name: "highlight",
   stops: [
    {position: 0.500, color: "#404040"},
    {position: 0.501, color: "#C0C0C0"},
    {position: 0.950, color: "#C0C0C0"},
    {position: 0.951, color: "#ffffff"}
   ]
  }
];

function CreateApp() {
  // Create Geometry.
  var sphereArrays = tdl.primitives.createSphere(0.4, 12, 8);
  var cubeArrays = tdl.primitives.createCube(0.5);
  var coneArrays = tdl.primitives.createTruncatedCone(
      0.4, 0.1, 0.8, 12, 1, true, true);
  var torusArrays = tdl.primitives.createTorus(0.4, 0.15, 20, 12);

  var c = document.createElement("canvas");
  c.width = 256;
  c.height = 1;
  var ctx = c.getContext("2d");
  ctx.fillStyle = "rgb(128, 128, 128)";
  ctx.fillRect(0, 0, 256, 1);
  ctx.fillStyle = "rgb(255,255,255)";
  ctx.fillRect(128, 0, 255, 1);

  var ramp = tdl.textures.loadTexture(c);
  ramp.setParameter(gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  // Load textures
  var textures = {
      ramp: ramp
  };
  g_rampTexture = ramp;

  // Create Shader Program
  var program = tdl.programs.loadProgramFromScriptTags(
      'sphereVertexShader',
      'sphereFragmentShader');

  // --Setup Models---------------------------------------
  var models = [
    new tdl.models.Model(program, torusArrays, textures)
  ];

  // -- Setup Instances ----------------------------------
  var instances = [];
  var spread = 6;
  for (var ii = 0; ii < 100; ++ii) {
    instances.push({
      x: (Math.random() - 0.5) * spread,
      y: (Math.random() - 0.5) * spread,
      z: (Math.random() - 0.5) * spread,
      colorMult: new Float32Array([Math.random(), Math.random(), Math.random(), 1]),
      modelIndex: Math.floor(Math.random() * models.length),
      axis: tdl.fast.normalize(new Float32Array(3), [Math.random(), Math.random(), Math.random()]),
      angle: Math.random() * Math.PI * 2,
      speed: (Math.random() + 0.5) * 0.5 * ((Math.random() > 0.5) ? 1 : -1),
    });
  }

  // pre-allocate a bunch of arrays
  var projection = new Float32Array(16);
  var view = new Float32Array(16);
  var world = new Float32Array(16);
  var worldInverse = new Float32Array(16);
  var worldInverseTranspose = new Float32Array(16);
  var viewProjection = new Float32Array(16);
  var worldViewProjection = new Float32Array(16);
  var viewInverse = new Float32Array(16);
  var viewProjectionInverse = new Float32Array(16);
  var eyePosition = new Float32Array(3);
  var target = new Float32Array(3);
  var up = new Float32Array([0,1,0]);
  var lightWorldPos = new Float32Array(3);
  var v3t0 = new Float32Array(3);
  var v3t1 = new Float32Array(3);
  var v3t2 = new Float32Array(3);
  var v3t3 = new Float32Array(3);
  var m4t0 = new Float32Array(16);
  var m4t1 = new Float32Array(16);
  var m4t2 = new Float32Array(16);
  var m4t3 = new Float32Array(16);
  var zero4 = new Float32Array(4);
  var one4 = new Float32Array([1,1,1,1]);

  // uniforms.
  var sharedUniforms = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    specular: one4,
    shininess: 50,
    specularFactor: 0.2,
  };
  var uniqueUniforms = {
    colorMult: new Float32Array([0,0,0,1]),
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose
  };

  var clock = 0.0;
  function update(elapsedTime) {
    clock += elapsedTime;

    // Make the camera rotate around the scene.
    eyePosition[0] = Math.sin(clock * g_eyeSpeed) * g_eyeRadius;
    eyePosition[1] = g_eyeHeight;
    eyePosition[2] = Math.cos(clock * g_eyeSpeed) * g_eyeRadius;

    // --Update Instance Positions---------------------------------------
    for (var ii = 0; ii < instances.length; ++ii) {
      var instance = instances[ii];
      instance.angle += instance.speed * elapsedTime;
    }
  }

  function render() {
    renderBegin();
    renderScene();
    renderEnd();
  }

  function renderBegin() {
    var m4 = fast.matrix4;
    // clear the screen.
    gl.colorMask(true, true, true, true);
    gl.depthMask(true);
    gl.clearColor(0,0,0,0);
    gl.clearDepth(1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);

    // Compute a projection and view matrices.
    m4.perspective(
        projection,
        math.degToRad(60),
        canvas.clientWidth / canvas.clientHeight,
        1,
        5000);
    m4.lookAt(
        view,
        eyePosition,
        target,
        up);
    m4.mul(viewProjection, view, projection);
    m4.inverse(viewInverse, view);
    m4.inverse(viewProjectionInverse, viewProjection);

    // Put the light near the camera
    m4.getAxis(v3t0, viewInverse, 0); // x
    m4.getAxis(v3t1, viewInverse, 1); // y
    m4.getAxis(v3t2, viewInverse, 2); // z
    fast.mulVectorVector(v3t0, g_lightOffset[0], v3t0);
    fast.mulScalarVector(v3t1, g_lightOffset[1], v3t1);
    fast.mulScalarVector(v3t2, g_lightOffset[2], v3t2);
    fast.addVector(lightWorldPos, eyePosition, v3t0);
    fast.addVector(lightWorldPos, lightWorldPos, v3t1);
    fast.addVector(lightWorldPos, lightWorldPos, v3t2);
  }

  function renderScene() {
    // -- Render Instances ---------------------------------------
    // sort by model.
    instances.sort(function(a, b) {
      if (a.modelIndex < b.modelIndex) return -1;
      if (a.modelIndex > b.modelIndex) return  1;
      return 0;
    });

    var m4 = fast.matrix4;
    var lastModel = null;
    for (var ii = 0; ii < instances.length; ++ii) {
      var instance = instances[ii];
      var model = models[instance.modelIndex];
      if (model != lastModel) {
        lastModel = model;
        model.drawPrep(sharedUniforms);
      }
      m4.translation(world, [instance.x, instance.y, instance.z]);
      m4.axisRotate(world, instance.axis, instance.angle);
      m4.mul(worldViewProjection, world, viewProjection);
      m4.inverse(worldInverse, world);
      m4.transpose(worldInverseTranspose, worldInverse);
      uniqueUniforms.colorMult = instance.colorMult;
      model.draw(uniqueUniforms);
    }
  }

  function renderEnd() {
    // Set the alpha to 255.
    gl.colorMask(false, false, false, true);
    gl.clearColor(0,0,0,1);
    gl.clear(gl.COLOR_BUFFER_BIT);
  }

  return {
    update: update,
    render: render
  };
}

function initialize() {
  math = tdl.math;
  fast = tdl.fast;
  canvas = document.getElementById("canvas");
  var fpsTimer = new tdl.fps.FPSTimer();
  var fpsElem = document.getElementById("fps");

  gl = tdl.webgl.setupWebGL(canvas);
  if (!gl) {
    return false;
  }

  var app = CreateApp();
  var then = (new Date()).getTime() * 0.001;

  function render() {
    requestAnimationFrame(render);

    // Compute the elapsed time since the last rendered frame
    // in seconds.
    var now = (new Date()).getTime() * 0.001;
    var elapsedTime = now - then;
    then = now;

    // Update the FPS timer.
    fpsTimer.update(elapsedTime);
    fpsElem.innerHTML = fpsTimer.averageFPS;

    app.update(elapsedTime);
    app.render();
  }
  render();
  return true;
}

$(function(){
  var canvas = document.createElement('canvas');
  canvas.width = 512;
  canvas.height = 1;

  $("#gradienteditor").gradientEditor({
    onChange: function(stops) {
      fillCanvasWithGradient(canvas, stops);
      g_rampTexture.setTexture(canvas);
    }
  });

  for (var ii = 0; ii < g_ramps.length; ++ii) {
    addRamp(g_ramps[ii]);
  }

  initialize();
  
  function fillCanvasWithGradient(canvas, stops) {
    var ctx = canvas.getContext("2d");
    var gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
    addGradientStops(gradient, stops);
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, canvas.width, 1);
  }

  function addGradientStops(gradient, stops) {
    for (var ii = 0; ii < stops.length; ++ii) {
      var stop = stops[ii];
      gradient.addColorStop(stop.position, stop.color);
    }
  }

  function addRamp(ramp) {
    var obj = $(
      '<div class="ramp">' +
        '<div class="ramp-name">' + ramp.name + '</div>' +
        '<div class="ramp-colors">' +
          '<canvas width="512" height="1"></canvas>' +
        '</div>' + 
      '</div>'
    );
    var canvasObj = $('canvas', obj);
    var canvas = canvasObj[0];
    fillCanvasWithGradient(canvas, ramp.stops);

    obj.click(function() {
      g_rampTexture.setTexture(canvas);
    });

    $('#ramps').append(obj);
  }
});

</script>
</head>
<body>
<div id="info">
  <div><a href="http://threedlibrary.googlecode.com" target="_blank">tdl.js</a> - flexible toon shading</div>
  <div>Edit the ramp or select one below to change effects</div>
</div>
<div id="fpsContainer">
  <div class="fps">fps: <span id="fps"></div>
</div>
<div id="uiContainer">
<div id="ui"></div>
  <div id="gradienteditor"></div>
  <div id="ramps"></div>
</div>
<div id="viewContainer">
<canvas id="canvas" width="1024" height="1024" style="width: 100%; height: 100%;"></canvas>
</div>
</body>
<script id="sphereVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform vec3 lightWorldPos;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  v_position = (worldViewProjection * position);
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  gl_Position = v_position;
}

</script>
<script id="sphereFragmentShader" type="text/something-not-javascript">
#ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif
uniform vec4 colorMult;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform sampler2D ramp;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec3 normal = normalize(v_normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                  dot(normal, halfVector), 1.0);
  gl_FragColor = vec4((colorMult * texture2D(ramp, vec2(litR.y, 0.5))).rgb, 1);
}
</script>
</html>



